Skip to content

feat: add McpClient wrapper for MCP client transport parity#96

Open
brendanjryan wants to merge 1 commit intomainfrom
feat/mcp-client-transport
Open

feat: add McpClient wrapper for MCP client transport parity#96
brendanjryan wants to merge 1 commit intomainfrom
feat/mcp-client-transport

Conversation

@brendanjryan
Copy link
Collaborator

@brendanjryan brendanjryan commented Mar 23, 2026

Summary

Adds McpClient, a payment-aware MCP client wrapper that brings Python to parity with the TypeScript McpClient.wrap API on the SDK features table.

What it does

McpClient wraps an MCP SDK ClientSession and overrides call_tool with automatic payment handling:

  1. Calls the tool normally
  2. If the server returns -32042 (Payment Required), parses the challenges
  3. Matches the challenge to an installed payment method by name + intent
  4. Creates a credential and retries the call with it in _meta
  5. Extracts the receipt from the result's _meta

Usage

from mcp import ClientSession
from mcp.client.sse import sse_client
from mpp.extensions.mcp import McpClient
from mpp.methods.tempo import tempo, TempoAccount, ChargeIntent

account = TempoAccount.from_key('0x...')
method = tempo(account=account, intents={'charge': ChargeIntent()})

async with sse_client(url) as streams:
    async with ClientSession(streams[0], streams[1]) as session:
        await session.initialize()
        client = McpClient(session, methods=[method])
        result = await client.call_tool('premium_tool', {'query': 'hello'})
        print(result.receipt)

brendanjryan added a commit to tempoxyz/mpp that referenced this pull request Mar 23, 2026
Mark Python MCP Client as supported (MCP SDK transport) following
tempoxyz/pympp#96 which adds the McpClient wrapper.
brendanjryan added a commit to tempoxyz/mpp that referenced this pull request Mar 23, 2026
Mark Python MCP Client as supported (MCP SDK transport) following
tempoxyz/pympp#96 which adds the McpClient wrapper.
@brendanjryan brendanjryan force-pushed the feat/mcp-client-transport branch from 6cd584c to 9111fcd Compare March 23, 2026 19:59
@chatgpt-codex-connector
Copy link

💡 Codex Review

if self._store is not None:
store_key = f"mpp:charge:{payload.hash.lower()}"
if not await self._store.put_if_absent(store_key, payload.hash):
raise VerificationError("Transaction hash already used")

P1 Badge Don't consume tx hashes before verification succeeds

With replay protection enabled, this marks the hash as used before any RPC or log checks run. If eth_getTransactionReceipt is temporarily unavailable, the transaction is still pending, or verification fails for any transient reason, the key is never cleared and every later retry will raise Transaction hash already used. That permanently rejects legitimate payments after recoverable failures whenever a store is configured.


pympp/src/mpp/store.py

Lines 18 to 22 in 6cd584c

async def put_if_absent(self, key: str, value: Any) -> bool:
"""Store *value* under *key* only if *key* does not already exist.
Returns ``True`` if the key was new and the write succeeded,
``False`` if the key already existed (duplicate).

P2 Badge Preserve compatibility with existing Store implementations

ChargeIntent accepts any exported Store, but this change makes put_if_absent() a new required method. Existing Redis/Dynamo/custom stores built against the previously published get/put/delete interface will now fail at runtime with AttributeError the first time hash replay protection is exercised, so this is a breaking change for current integrations rather than an internal refactor.


name: str,
arguments: dict[str, Any] | None = None,
*,
timeout: float | None = None,
meta: dict[str, Any] | None = None,

P2 Badge Forward progress callbacks through the MCP wrapper

ClientSession.call_tool() supports a progress_callback for long-running tools, but the wrapper drops that parameter entirely. Any caller migrating from session.call_tool(...) to McpClient.call_tool(...) can no longer pass the callback, so paid tools lose progress notifications (or the caller gets a TypeError if they keep the old call shape).

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Add McpClient that wraps an MCP SDK ClientSession with automatic
payment handling - matching the TypeScript McpClient.wrap API.

When call_tool gets a -32042 error, McpClient:
1. Parses challenges from error.data
2. Matches to an installed payment method by name+intent
3. Creates a credential and retries with it in _meta
4. Extracts receipts from the result

- New: src/mpp/extensions/mcp/client.py (McpClient, McpToolResult)
- Export McpClient and McpToolResult from mpp.extensions.mcp
- 22 new tests covering free/paid tools, method matching,
  receipt extraction, error propagation, meta forwarding
- Updated example client to use McpClient instead of manual flow
- Updated example README
@brendanjryan brendanjryan force-pushed the feat/mcp-client-transport branch from 9111fcd to 31a5de2 Compare March 23, 2026 20:58
@brendanjryan brendanjryan requested a review from Slokh March 23, 2026 22:07
@brendanjryan brendanjryan added the cyclops Trigger Cyclops PR audit label Mar 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cyclops Trigger Cyclops PR audit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant